<template>
  <div ref="canvas"></div>
</template>

<script lang="ts" setup>
import * as THREE from "three";
import Stats from "three/examples/jsm/libs/stats.module";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";
import { ref, onMounted } from "vue";

const canvas = ref();
// 场景
const scene = new THREE.Scene();
// 渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// 相机
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);
camera.position.set(-30, 40, 50);
camera.lookAt(0, 0, 0);
// 相机控件
const orbitControl = new OrbitControls(camera, renderer.domElement);
// 性能统计
const stat = Stats();
// create points
const createTexture = (): THREE.Texture => {
  const canvas = document.createElement('canvas');
  canvas.width = 16;
  canvas.height = 16;

  const context = canvas.getContext('2d');
  const gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2);
  gradient.addColorStop(0, 'rgba(255,255,255,1)');
  gradient.addColorStop(0.2, 'rgba(0,255,255,1)');
  gradient.addColorStop(0.4, 'rgba(0,0,64,1)');
  gradient.addColorStop(1, 'rgba(0,0,0,1)');

  context.fillStyle = gradient;
  context.fillRect(0, 0, canvas.width, canvas.height);

  const texture = new THREE.Texture(canvas);
  texture.needsUpdate = true;
  return texture;
};
const createPoints = (geom): THREE.Points => {
  const material = new THREE.PointsMaterial({
    color: 0xffffff,
    size: 3,
    transparent: true,
    blending: THREE.AdditiveBlending,
    map: createTexture(),
    depthWrite: false // instead of sortParticles
  });
  return new THREE.Points(geom, material);
};
// gui
const controls = {
  radius: 13,
  tube: 1.7,
  radialSegments: 156,
  tubularSegments: 12,
  p: 5,
  q: 4,
  asParticles: false,
  rotate: true,
  redraw: () => {
    if (scene.getObjectByName("target")) {
      scene.remove(scene.getObjectByName("target"));
    }
    const geom = new THREE.TorusKnotGeometry(
        controls.radius,
        controls.tube,
        Math.round(controls.radialSegments),
        Math.round(controls.tubularSegments),
        Math.round(controls.p),
        Math.round(controls.q));

    let mesh;
    if (controls.asParticles) {
      mesh = createPoints(geom);
    } else {
      mesh = new THREE.Mesh(geom, new THREE.MeshNormalMaterial());
    }
    mesh.name = "target";
    scene.add(mesh);
  },
};
const gui = new GUI();
gui.add(controls, 'radius', 0, 40).onChange(controls.redraw);
gui.add(controls, 'tube', 0, 40).onChange(controls.redraw);
gui.add(controls, 'radialSegments', 0, 400).step(1).onChange(controls.redraw);
gui.add(controls, 'tubularSegments', 1, 20).step(1).onChange(controls.redraw);
gui.add(controls, 'p', 1, 10).step(1).onChange(controls.redraw);
gui.add(controls, 'q', 1, 15).step(1).onChange(controls.redraw);
gui.add(controls, 'asParticles').onChange(controls.redraw);
gui.add(controls, 'rotate').onChange(controls.redraw);

controls.redraw();

const render = () => {
  stat.update();

  if (controls.rotate) {
    scene.getObjectByName("target").rotation.y += 0.005;
  }

  requestAnimationFrame(render);
  renderer.render(scene, camera);
};
onMounted(() => {
  canvas.value.appendChild(stat.domElement);
  canvas.value.appendChild(renderer.domElement);
  render();
})
</script>
